class Parser
  SYSREGS=%w[RS RC0]

  class Code < Array
    def initialize
      super
      @cnum=0
    end
    attr_accessor :cnum

    def addCON(k=0)
      out=[]
      k+=@cnum

      each do |a|
        if a.instance_of?(Code)
          out.concat a.addCON(k)
        elsif a=='CON'
          out.push "con #{k}"
        else
          out.push a
        end
      end

      out
    end
  end

  class Label
    def initialize
      @i=-1
    end

    def next
      @i+=1
      "L#{@i}"
    end
  end

  def ttype(n=0)
    @tok[n] ? @tok[n][0] : nil
  end

  def chktok(type)
    perror() unless ttype()==type
    ret=@tok[0][1]
    @tok.shift
    ret
  end

  def perror
    raise @tok.empty? ? "cannot get next token" : "parse error at #{ttype()}"
  end

  def parse(tok)
    @tok=tok
    @lab=Label.new
    @gvars={}; @funcs={}; @arrs={};
    @macros={}
    (0...SYSREGS.size).each do |i|
      @macros[SYSREGS[i]]=i
    end

    dcode=Code.new
    while true
      case ttype()
      when :FUNCTION
        dcode << fdecl()
      when :ARRAY
        adecl()
      else
        break
      end
    end

    @lvars={}; @ln=0; @fid='MAIN'; @tlabs={}
    mcode=t()
    perror() unless @tok.empty?

    @funcs.each {|k,v| raise "function #{k} is not defined" unless v[1]}
    @tlabs.each {|k,v| raise "label #{k} is not defined in #{@fid}" unless v }

    acode=Code.new
    @arrs.each do |k,v|
      raise "array #{k} is not declared" unless v
      @macros["A#{k}"]=SYSREGS.size+@gvars.size+acode.size-1
      @macros["AS#{k}"]=v.size
      v.each {|i| acode.push "con #{i}" }
    end

    code=Code.new
    code.push 'sal '+(SYSREGS.size+@gvars.size).to_s
    code.push acode
    code.push "jmp L!#{@fid}"
    code.push dcode
    code.push "L!#{@fid}"
    code.push 'con '+(SYSREGS.size+@gvars.size+acode.size).to_s
    code.push 'gto RS'
    code.push 'sal '+@lvars.size.to_s
    mcode.cnum+=@lvars.size; code.push mcode
    code.push 'hal 0'

    [code.addCON,@macros]
  end

  def sep
    case ttype()
    when :NL
      @tok.shift
    when :SEMI
      @tok.shift
      @tok.shift if ttype()==:NL
    else
      perror()
    end
  end

  def fdecl
    chktok(:FUNCTION)
    @fid=chktok(:ID); @lvars={}; @ln=0; @tlabs={}
    chktok(:LPAR)
    pnum=args()
    chktok(:RPAR)
    sep()
    bo=t()
    chktok(:END)
    sep()

    @tlabs.each {|k,v| raise "label #{k} is not defined in #{@fid}" unless v }

    if @funcs[@fid]
      raise "function #{@fid} is already defined" if @funcs[@fid][1]
      raise "argument number error (#{@fid})" unless pnum==@funcs[@fid][0]
      @funcs[@fid][1]=true
    else
      @funcs[@fid]=[pnum,true]
    end

    ret=Code.new
    ret.push 'L!'+@fid
    ret.push 'sal '+(@lvars.size-1-pnum).to_s
    bo.cnum+=@lvars.size-1-pnum; ret.push bo
    (@lvars.size-1).times { ret.push 'pop' }
    SYSREGS.reverse_each {|i| ret.push 'gto '+i }
    ret.push 'jpo'
  end

  def args
    if ttype()!=:ID
      @lvars[@fid]=-(3+SYSREGS.size)
      return 0
    end

    a=[]
    while true
      id=chktok(:ID)
      raise "variable #{id} already exists" if id==@fid or a.include?(id)
      a.push id
      ttype()==:COMMA ? @tok.shift : break
    end

    a.each_index {|i| @lvars[a[i]]=i-a.size}
    @lvars[@fid]=-(3+SYSREGS.size+a.size)
    a.size
  end

  def adecl
    chktok(:ARRAY)
    id=chktok(:ID)
    raise "array #{id} is already declared" if @arrs[id]

    case ttype()
    when :LBRA
      @tok.shift
      k=anum().to_i
      raise 'array size must be positive' unless k>0
      chktok(:RBRA)
      sep()
      a=Array.new(k,0)
    when :DEF
      @tok.shift
      chktok(:LBRA)
      a=[anum()]
      while ttype()==:COMMA
        @tok.shift
        a << anum()
      end
      chktok(:RBRA)
      sep()
    else
      perror()
    end
    @arrs[id]=a
  end

  def anum
    case ttype()
    when :PLUS
      @tok.shift
      chktok(:NUM)
    when :MINUS
      @tok.shift
      '-'+chktok(:NUM)
    else
      chktok(:NUM)
    end
  end

  def t
    ret=Code.new
    while true
      case ttype()
      when :WRITE
        ret << writes()
      when :ID
        ret << (ttype(1)==:LPAR ? proc() : dain())
      when :DOLLAR
        ret << dain()
      when :READ
        ret << reads()
      when :IF
        ret << ifs()
      when :WHILE
        ret << whiles()
      when :GOTO
        ret << gotos()
      when :LABEL
        ret << labels()
      else
        break
      end
    end
    ret
  end

  def proc
    ret=func()
    sep()
    ret.push 'pop'
  end

  def writes
    chktok(:WRITE)
    ret=e()
    sep()
    ret.push 'pri'
  end

  def whiles
    chktok(:WHILE)
    co=conds()
    sep()
    bo=t()
    chktok(:END)
    sep()

    l0=@lab.next; l1=@lab.next
    ret=Code.new
    ret.push l0
    ret.push co
    ret.push 'jpo '+l1
    ret.push bo
    ret.push 'jmp '+l0
    ret.push l1
  end

  def gotos
    chktok(:GOTO)
    id=chktok(:ID)
    sep()
    @tlabs[id]=false unless @tlabs.key?(id)
    Code.new.push "jmp L!#{@fid}@#{id}"
  end

  def labels
    chktok(:LABEL)
    id=chktok(:ID)
    sep()
    raise "label #{id} is already appeared in #{@fid}" if @tlabs[id]
    @tlabs[id]=true
    Code.new.push "L!#{@fid}@#{id}"
  end

  def dain
    if ttype()==:DOLLAR
      @tok.shift
      id=chktok(:ID)
      chktok(:DEF)
      ret=e()
      sep()

      @gvars[id]=SYSREGS.size+@gvars.size unless @gvars.key?(id)
      ret.push "gto #{@gvars[id]}"
      return ret
    end

    id=chktok(:ID)
    case ttype()
    when :DEF
      @tok.shift
      ret=e()
      sep()

      unless @lvars.key?(id)
        @lvars[id]=@ln
        @ln+=1
      end
      ret.push "con #{@lvars[id]}"
      ret.push 'god RS'
      ret.push 'add'
      ret.push 'gto'
    when :LBRA
      @tok.shift
      ind=e()
      chktok(:RBRA)
      chktok(:DEF)
      ret=e()
      sep()

      @arrs[id]=nil unless @arrs.key?(id)
      ret.push 'con A'+id
      ind.cnum+=2; ret.push ind
      l0=@lab.next; l1=@lab.next;
      ret.push 'gto RC0'
      ret.push 'god RC0'
      ret.push 'jpo '+l1
      ret.push l0
      ret.push 'hal 1'
      ret.push l1
      ret.push 'god RC0'
      ret.push 'con AS'+id
      ret.push 'sub'
      ret.push 'jpo '+l0
      ret.push 'god RC0'
      ret.push 'add'
      ret.push 'gto'
    else
      perror()
    end
  end

  def reads
    chktok(:READ)
    if ttype()==:DOLLAR
      @tok.shift
      id=chktok(:ID)
      sep()

      @gvars[id]=SYSREGS.size+@gvars.size unless @gvars.key?(id)
      ret=Code.new
      ret.push 'red'
      ret.push "gto #{@gvars[id]}"
      return ret
    end

    id=chktok(:ID)
    if ttype()==:LBRA
      @tok.shift
      ind=e()
      chktok(:RBRA)
      sep()

      @arrs[id]=nil unless @arrs.key?(id)
      ret=Code.new
      ret.push 'red'
      ret.push 'con A'+id
      ind.cnum+=2; ret.push ind
      l0=@lab.next; l1=@lab.next;
      ret.push 'gto RC0'
      ret.push 'god RC0'
      ret.push 'jpo '+l1
      ret.push l0
      ret.push 'hal 1'
      ret.push l1
      ret.push 'god RC0'
      ret.push 'con AS'+id
      ret.push 'sub'
      ret.push 'jpo '+l0
      ret.push 'god RC0'
      ret.push 'add'
      ret.push 'gto'
    else
      sep()
      unless @lvars.key?(id)
        @lvars[id]=@ln
        @ln+=1
      end
      ret=Code.new
      ret.push 'red'
      ret.push "con #{@lvars[id]}"
      ret.push 'god RS'
      ret.push 'add'
      ret.push 'gto'
    end
  end

  def ifs
    chktok(:IF)
    ret=conds()
    sep()
    bo=t()

    lend=@lab.next; l=@lab.next
    ret.push 'jpo '+l
    ret.push bo
    ret.push 'jmp '+lend
    ret.push l

    while ttype()==:ELSIF
      @tok.shift
      co=conds()
      sep()
      bo=t()

      l=@lab.next
      ret.push co
      ret.push 'jpo '+l
      ret.push bo
      ret.push 'jmp '+lend
      ret.push l
    end

    case ttype()
    when :ELSE
      @tok.shift
      sep()
      bo=t()
      chktok(:END)
      sep()

      ret.push bo
      ret.push lend
    when :END
      @tok.shift
      sep()
      ret.push lend
    else
      perror()
    end
  end

  def conds
    ret=et()
    while ttype()==:OU
      @tok.shift
      c=et()
      l1=@lab.next; l2=@lab.next
      ret.push 'jpo '+l1
      ret.push 'con  0'
      ret.push 'jmp '+l2
      ret.push l1
      ret.push c
      ret.push l2
    end
    ret
  end

  def et
    ret=cond()
    while ttype()==:ET
      @tok.shift
      c=cond()
      l1=@lab.next; l2=@lab.next
      ret.push 'jpo '+l1
      ret.push c
      ret.push 'jmp '+l2
      ret.push l1
      ret.push 'con 1'
      ret.push l2
    end
    ret
  end

  def cond
    case ttype()
    when :LPAR
      p=1
      for q in 1...@tok.size
        case ttype(q)
        when :EQ,:NE,:GT,:GE,:LT,:LE,:ET,:OU,:NON,:VRAI,:FAUX
          @tok.shift
          ret=conds()
          chktok(:RPAR)
          return ret
        when :LPAR
          p+=1
        when :RPAR
          p-=1
          break if p==0
        end
      end
      perror() if p!=0
    when :NON
      @tok.shift
      v=cond()
      ret=Code.new
      ret << 'con 1'
      v.cnum+=1; ret << v
      ret << 'sub'
      return ret
    when :VRAI
      @tok.shift
      return Code.new.push('con 0')
    when :FAUX
      @tok.shift
      return Code.new.push('con 1')
    end

    e1=e()
    op=ttype()
    perror() unless [:EQ,:NE,:GT,:GE,:LT,:LE].include? op
    @tok.shift
    e2=e()

    case op
    when :EQ
      ret=e1
      l1=@lab.next; l2=@lab.next
      e2.cnum+=1; ret.push e2
      ret.push 'sub'
      ret.push 'gto RC0'
      ret.push 'god RC0'
      ret.push 'jpo '+l1
      ret.push 'con 0'
      ret.push 'god RC0'
      ret.push 'sub'
      ret.push 'jmp '+l2
      ret.push l1
      ret.push 'con 1'
      ret.push l2
    when :NE
      ret=e1
      l1=@lab.next; l2=@lab.next
      e2.cnum+=1; ret.push e2
      ret.push 'sub'
      ret.push 'gto RC0'
      ret.push 'god RC0'
      ret.push 'jpo '+l1
      ret.push 'god RC0'
      ret.push 'con 1'
      ret.push 'add'
      ret.push 'jmp '+l2
      ret.push l1
      ret.push 'con 0'
      ret.push l2
    when :GT
      ret=e2
      e1.cnum+=1; ret.push e1
      ret.push 'sub'
      ret.push 'con 1'
      ret.push 'add'
    when :GE
      ret=e2
      e1.cnum+=1; ret.push e1
      ret.push 'sub'
    when :LT
      ret=e1
      e2.cnum+=1; ret.push e2
      ret.push 'sub'
      ret.push 'con 1'
      ret.push 'add'
    when :LE
      ret=e1
      e2.cnum+=1; ret.push e2
      ret.push 'sub'
    end
  end

  def e
    ret=f()
    while true
      case ttype()
      when :PLUS
        @tok.shift
        v=f()
        v.cnum+=1
        ret << v << 'add'
      when :MINUS
        @tok.shift
        v=f()
        v.cnum+=1
        ret << v << 'sub'
      else
        break
      end
    end
    ret
  end

  def f
    ret=g()
    while true
      case ttype()
      when :MUL
        @tok.shift
        v=g()
        v.cnum+=1
        ret << v << 'mul'
      when :DIV
        @tok.shift
        v=g()
        v.cnum+=1
        ret << v << 'div'
      when :MOD
        @tok.shift
        v=g()
        ret.push 'gto RC0'
        ret.push 'god RC0'
        ret.push 'god RC0'
        v.cnum+=2; ret.push v
        ret.push 'gto RC0'
        ret.push 'god RC0'
        ret.push 'div'
        ret.push 'god RC0'
        ret.push 'mul'
        ret.push 'sub'
      else
        break
      end
    end
    ret
  end

  def g
    case ttype()
    when :NUM
      t=chktok(:NUM)
      Code.new.push 'con '+t
    when :PLUS
      @tok.shift
      g()
    when :MINUS
      @tok.shift
      v=g()
      if v.size==1 and v[0]=~/\Acon\s+(-?\d+)\z/
        Code.new.push "con #{-($~[1].to_i)}"
      else
        v.cnum+=1
        Code.new.push 'con 0', v, 'sub'
      end
    when :DOLLAR
      @tok.shift
      id=chktok(:ID)
      @gvars[id]=SYSREGS.size+@gvars.size unless @gvars.key? id
      Code.new.push "god #{@gvars[id]}"
    when :PIPE
      @tok.shift
      id=chktok(:ID)
      chktok(:PIPE)
      @arrs[id]=nil unless @arrs.key?(id)
      Code.new.push 'con AS'+id
    when :LPAR
      @tok.shift
      ret=e()
      chktok(:RPAR)
      ret
    when :ID
      case ttype(1)
      when :LPAR
        func()
      when :LBRA
        id=chktok(:ID)
        chktok(:LBRA)
        v=e()
        chktok(:RBRA)

        @arrs[id]=nil unless @arrs.key?(id)
        ret=Code.new
        ret.push 'con A'+id
        v.cnum+=1; ret.push v
        l0=@lab.next; l1=@lab.next;
        ret.push 'gto RC0'
        ret.push 'god RC0'
        ret.push 'jpo '+l1
        ret.push l0
        ret.push 'hal 1'
        ret.push l1
        ret.push 'god RC0'
        ret.push 'con AS'+id
        ret.push 'sub'
        ret.push 'jpo '+l0
        ret.push 'god RC0'
        ret.push 'add'
        ret.push 'god'
      else
        id=chktok(:ID)
        unless @lvars.key?(id)
          @lvars[id]=@ln
          @ln+=1
        end
        ret=Code.new
        ret.push "con #{@lvars[id]}"
        ret.push 'god RS'
        ret.push 'add'
        ret.push 'god'
      end
    else
      perror()
    end
  end

  def func
    id=chktok(:ID)
    chktok(:LPAR)
    p=params()
    chktok(:RPAR)

    k=p.size
    if @funcs[id]
      raise "argument number error (#{id})" unless k==@funcs[id][0]
    else
      @funcs[id]=[k,false]
    end

    l0=@lab.next
    ret=Code.new
    ret.push 'con 0'
    ret.push 'con 1'
    ret.push 'con '+l0
    SYSREGS.each {|j| ret.push 'god '+j }
    ret.push p
    ret.push 'god RS'
    c=Code.new.push 'CON'; c.cnum+=3+SYSREGS.size+k; ret.push c
    ret.push 'add'
    ret.push 'gto RS'
    ret.push 'jmp L!'+id
    ret.push l0
  end

  def params
    return Code.new if ttype()==:RPAR
    ret=Code.new
    ret.cnum=3+SYSREGS.size
    while true
      v=e()
      v.cnum+=ret.size
      ret << v
      ttype()==:COMMA ? @tok.shift : break
    end
    ret
  end
end

require 'strscan'

def lex(file)
  f=open(file)

  tok=[]
  s=StringScanner.new('')
  while str=f.gets
    s.string=str
    a=[]
    until s.eos?
      case
      when s.scan(/\#/)
        s.terminate
      when s.scan(/\s+/)
      when s.scan(/[a-z][a-z0-9]*/)
        case s.matched
        when 'function'
          a.push [:FUNCTION,0]
        when 'write'
          a.push [:WRITE,0]
        when 'read'
          a.push [:READ,0]
        when 'while'
          a.push [:WHILE,0]
        when 'if'
          a.push [:IF,0]
        when 'else'
          a.push [:ELSE,0]
        when 'elf'
          a.push [:ELSIF,0]
        when 'end'
          a.push [:END,0]
        when 'array'
          a.push [:ARRAY,0]
        when 'et'
          a.push [:ET,0]
        when 'ou'
          a.push [:OU,0]
        when 'non'
          a.push [:NON,0]
        when 'goto'
          a.push [:GOTO,0]
        when 'label'
          a.push [:LABEL,0]
        when 'vrai'
          a.push [:VRAI,0]
        when 'faux'
          a.push [:FAUX,0]
        else
          a.push [:ID,s[0]]
        end
      when s.scan(/0*(\d+)/)
        a.push [:NUM,s[1]]
      when s.scan(/:=/)
        a.push [:DEF,0]
      when s.scan(/></)
        a.push [:NE,0]
      when s.scan(/>=/)
        a.push [:GE,0]
      when s.scan(/<=/)
        a.push [:LE,0]
      when s.scan(/=/)
        a.push [:EQ,0]
      when s.scan(/>/)
        a.push [:GT,0]
      when s.scan(/</)
        a.push [:LT,0]
      when s.scan(/\+/)
        a.push [:PLUS,0]
      when s.scan(/\-/)
        a.push [:MINUS,0]
      when s.scan(/\*/)
        a.push [:MUL,0]
      when s.scan(/\//)
        a.push [:DIV,0]
      when s.scan(/%/)
        a.push [:MOD,0]
      when s.scan(/;/)
        a.push [:SEMI,0]
      when s.scan(/,/)
        a.push [:COMMA,0]
      when s.scan(/\$/)
        a.push [:DOLLAR,0]
      when s.scan(/\(/)
        a.push [:LPAR,0]
      when s.scan(/\)/)
        a.push [:RPAR,0]
      when s.scan(/\[/)
        a.push [:LBRA,0]
      when s.scan(/\]/)
        a.push [:RBRA,0]
      when s.scan(/\|/)
        a.push [:PIPE,0]
      else
        raise 'unknown token at "'+s.string.chomp+'"'
      end
    end

    unless a.empty?
      tok.concat a
      tok.push [:NL,0]
    end
  end

  f.close
  tok
end

def asm(code,mac)
  out=[]

  mac.each_key do |k|
    raise "macro name error (#{k})" unless k=~/\A[A-KM-Z]\S*\z/
  end

  code.each {|u|
    unless u=~/\A([a-z]{3})\s+(.+)\z/
      out.push u
      next
    end

    m2=$~[2]
    case $~[1]
    when 'sal'
      m2=mac[m2] if mac.key? m2
      raise "cannot expand '#{u}'" unless m2=~/\A\+?\d+\z/
      m2.to_i.times { out.push 'con 0' }
    when 'god'
      out.push 'con '+m2
      out.push 'god'
    when 'gto'
      out.push 'con '+m2
      out.push 'gto'
    when 'jpo'
      out.push 'con '+m2
      out.push 'jpo'
    when 'jmp'
      out.push 'con 1'
      out.push 'con '+m2
      out.push 'jpo'
    when 'hal'
      out.push 'con '+m2
      out.push 'hal'
    else
      out.push u
    end
  }

  rep=[]
  j=nil
  lt=[]
  out.each_index {|i|
    if out[i]=~/\AL\S*\z/
      raise "label #{out[i]} is already appeared" if lt.include?(out[i])
      lt << out[i]

      if j
        rep.push [out[i],j]
        out[i]=nil
      else
        j=out[i]
      end
    else
      j=nil
    end
  }  
  out.compact!
  rep.each {|f,t|
    rex=/(\A|\s)#{Regexp.escape(f)}(?=\z|\s)/
    out.collect! {|a|
      a.gsub(rex) { $1+t }
    }
  }
  (3...out.size).each {|i|
    if out[i-3]=='con 1' and out[i-2]=~/\Acon\s+(L\S*)\z/ and out[i-1]=='jpo' and out[i]==$~[1]
      out[i-3..i-1]=nil
    end
  }
  out.compact!

  j=0
  for i in 0...out.size
    if out[i]=~/\AL\S*\z/
      mac[out[i]]=j
      out[i]=nil
    else
      j+=1
    end
  end
  out.compact!

  out.collect! do |a|
    a.gsub(/(\A|\s)([A-Z]\S*)/) { mac.key?($2) ? $1+mac[$2].to_s: $& }
  end

  out.collect! do |a|
    a=~/\Acon\s+([\-+]?\d+)\z/ ? $~[1] : a
  end
end

if ARGV.size==1
  fin=ARGV[0]
  fout='epro.code'
elsif ARGV.size==3 and ARGV[1]=='-o'
  fin=ARGV[0]
  fout=ARGV[2]
elsif ARGV.size==3 and ARGV[0]=='-o'
  fin=ARGV[2]
  fout=ARGV[1]
else
  abort "usage: ruby #{$0} source [-o code]"
end

begin
  tok=lex(fin)
  code,mac=Parser.new.parse(tok)
  prog=asm(code,mac)
  open(fout,'w') {|f|
    prog.each {|i| f.puts i}
  }
rescue
  abort $!
end
